Canvas
Mozilla's tutorialMy
first attempt was to use the new <canvas> element, While I spent a little time to
getting this to work, It became quite clear very early that drawing text on
the graphics area is currently not possible (although you can use
various hacks to take screenshots of hidden areas with text on, and
overlay them on the canvas) - another option may be using XUL stack or
similar in HTML to overlay text ontop of the graphics - but this all
seemed like one kludge to many for my calendar.
One of the
attractions of Canvas over SVG is that the drawing code in javascript
is slightly cleaner (eg. you dont have to keep creating DOM nodes to
draw stuff). but as I later discovered, that is really a very small
part of doing interactive applications in SVG.
And finally to SVG
So
after abandoning my Canvas effort, I began on my explorations with SVG.
Most of the code still inherit's a ghost of the first efforts, as I used
the variable name 'canvas' to represent the SVG DOM node.
Over
the week, it must have taken to build the first working version, I ran
into quite a few quirks of the current implemenation in Firefox, but in
the end managed to turn my unmaintainable code from the first XUL calendar
into something considerably more usable, and flexible.
Speed
SVG,
while not dog slow, is a touch slower than pure XUL for the calendar.
It is usable, and I expect it will probably improve over time, as by
the looks of things, SVG support was added to get more real world
testing.
My XUL calendar had also begun to suffer from speed
issues in Firefox 1.5, although alot of this was due to the event model
I had used. In the XUL calendar, I had used
{node}.addEventListener('mousedown'....) etc. to all the calendar
entries as i created them. Then also added an event listener to the
background grid that made up the calendar. My guess is this complex
tree of event handlers which could be called slowed down the event
handler in firefox. So for the SVG version, I only added a single set
of event handlers to the top level <svg> node.
When
these event handlers are fired, they recieve the event.target, and
decide which event hander object to instantate to deal with the event.
(normally on mouse down). The main handlers are
Drag/Resize/Create/Edit/ProjectSelect. each one has it's own class
which handle all the later events (like mousemove/up/keypress etc.),
These all extend from a dummy base class, which does nothing in most
cases (so I dont have to keep checking if there is a current event
handler method) This change is probably the most significant from the
original XUL code, and greatly enhanced the readability of the code.
Debugging
One
of the first bugbears of working with javascript has always been
debugging. While Firefox has quite a few tools, The Javascript Console
being essential (now greatly enhanced with CSS error messages). I find
the JS debugger a bit of an overkill. I have been far too used over the
years to doing 'print_r($somevar)" or similar in PHP to see what is
going on when. and while javascript's alert() is usefull for the
occasional debugging message, it can get a bit annoying when testing.
(Especially if you accidentally loop for ever doing alert()).
After
a bit of googling I came across the idea of debugging to another window
- while that sounded reasonable. I guess tabbed browsing and popup
blockers have numbed me to the attraction of them. So I slightly
modified the concept to write to a <PRE> element within the body
of the document. - this one's for the library:
function showDebug(str)
{
if (typeof(debug_node) == 'undefined') {
debug_node = document.getElementById('debug');
// now == null if not found!
}
if (!debug_node) {
return;
}
var data = document.createTextNode(str+"\n");
var before = null;
if (debug_node.childNodes.length) {
before = debug_node.childNodes[0];
}
debug_node.insertBefore(data, before);
if (debug_node.childNodes.length > 50) {
debug_node.removeChild(debug_node.childNodes[50]);
}
}
And to make it work, in XUL I add
<html:pre id="debug" style="height:100px;width:100%; overflow: auto;">
hello world
</html:pre>
Basically use DOM add node's to add children to the PRE tag.
Drawing in SVG
The
actual drawing component of SVG is quite simple, you just create DOM
nodes, and add them to your <svg> or <g> elements.
Using the XML view from Inkscape helps quite a bit, although Firefox doesnt support quite as many tags as Inkscape. This
example is from the method that draws horizontal lines in the calendar.
for (var i = 0;i < 18; i++) {
var shape = document.createElementNS(svgns, "line");
shape.setAttribute("x1", canvas.hour.leftmargin);
shape.setAttribute("y1", canvas.hour.height * i);
shape.setAttribute("x2", this.canvasbox.width);
shape.setAttribute("y2", canvas.hour.height * i);
shape.setAttribute("style",
"stroke:#cff;fill:#cff;stroke-width:5");
group.appendChild(shape);
}
I
also tended to add extra attributes (eg. "cal:id") to the group
describing a calendar event, and add the javascipt object describing
the event to the dom node group (eg. g_domnode.calendarEvent = this;)
Dragging is done by mousemove events and changing the transform attribute of the group containing the calendar event.
this.g.setAttribute("transform", "translate(" +
this.g.getAttribute("cal:x") + ',' +
this.g.getAttribute("cal:y") + ')');
Resizing
is a little more complex, in that it has to resize the body rectangle,
along with moving the time information, and a line I added to indicate
that you are resizing.
Text issues
Text on SVG (in
Firefox's current implementation) is a little tricky. as there is no
<foriegnobject> support, you can not embed a <div> of text. you
have to create text nodes.
Text nodes unfortunatly do not wrap,
and require a little bit of work to clip. For the body description on
the calendar event, we effectively build this svg
<clipPath id="cp123">
<rect x="2" y="50" width="80" height="120">
</clipPath>
<g clip-path="url(#cp123)">
<text x="5" y="10" fill="#000" style="font-style.....">
.. more text nodes....
</g>
We then adjust the clipPath/rect when we resize the event.
Interaction - Text editing
The two key differences between my original Calendar, and the one you get in Sunbird where
- editing inline (double click and you edit the text in the box, rather than a silly popup appearing)
- project tagging of events - for use later will billing/review apps.
The
editing inline makes a huge difference from a usability perspective,
but proved a bit of a challenge with SVG/XUL. In the original XUL
calendar, a <stack> is used to position a editing calendar entry
ontop of the shown one, then hide the original.
SVG does not
provide any editing widgets, and as I mentioned before, there is
currently no <foriegnobject> support. So I basically did the same
trick as before, use a <stack> to position a <textbox>
ontop of the calendar event (and a little clever maths to get the
location and size correct.
The result however is a little odd,
the cursor does not flash when you overlay the textbox ontop of the SVG
area, and there is no natrual visual indication that you are editing. (other than it changes when you type...) -
To try and alieviate this, I just thickened the border of the editing
area.
Scaling the graphics - and wonky layout.
If you have ever
loaded a svg url into firefox 1.5, you will notice that most of the
time the graphic ends up huge, and you have to scroll around the window
to see the picture. When embedding an graphic into a web page, you use
the <svg> tag which has a few usefull attributes:
<svg viewBox="0 0 1200 600" preserveAspectRatio="none"
width="98%" height="600">
- viewBox tels the browser to scale the coordinates so that you can see that range within the box containing the svg element.
- preserveAspectRatio can be used to tell the renderer if you want the
x/y ration to be fixed or scaled. If you are displaying text inside the
image, then it's usually not a good idea to stretch the aspect ratio.
I tended to find that using 98% as the width prevented a scrollbar
appearing at the bottom of the screen. However to get the layout
correct, we have to manually query the pixel width of the svg, and
re-adjust the viewBox to match the actual width in javascript on the window.onload event.
However for some reason after doing this, the layout did not render correctly
until you edited a event (overlaying the textbox) - then making it
disappear. - In the end, I faked this by going through this process at
the onload stage.
Talking to the backend
Having just spend a few months working with SOAP, this is one technology which should have been burnt at the stake the day it was born!. - dumb idea, from start to finish (undebuggable, slow, uncachable). - So that definatly got ruled out ;)
Almost all the "AJAX"(and I still hate that acronym) code I'm doing sends HTTP GET/POST requests in standard syntax (eg. just like a HTML form).. When you move the event, it sends a url with day=3&startdate=2005-12-01&... in the body. This makes the backend code clean, simple, and easy to debug.
At present I'm sending XML back from the server, JSON may be a possibilty here, but I'm not too keen on running eval() on the client side, or including an extra library, when DOM parsing is so simple.
Let's have a play?
you can have a play with it by visiting
the bug tracker, and just logging in as guest (press the guest button)
- creating entries is done by selecting a timeblock click and move, then pressing any key.
- drag either the top or bottom to resize / move. and double click to edit.
- change colour (or really project) by right hand mouse clicking on the top menu bit.
If it's not working, It's probably as I'm hacking on it..
The rest of the bug tracker is pretty broken at present.. - It's an
ongoing disaster, that I toy with occasionally - one day you will be
able to report bugs ;)
If you want to explore the code, either look at the
svn/xul viewer under FlexyBugs/FlexyBugs/templates/week.xul and FlexyBugs/FlexyBugs/templates/images/*.js
or browse to the svn on-line
http://www.akbkhome.com/svn/FlexyBugs/FlexyBugs/templates/
The PHP backend code for it is pretty simple
http://www.akbkhome.com/svn/FlexyBugs/FlexyBugs/Calendar/
although this does not include the queries related to the Project database.
TODO's
There's quite a few todo's on the list, some may get done quicker that others
- editing 1/2 hour events is probably the biggest - it's done in the XUL version, by having a minimum textbox size.
- stacking, I think would had hugely to the visual appeal.
- merge dragging and resizing, and base it on mousedown position, rather than rectangle...
- lots more..
Interesting technology.. - things like project managment tools look feasible..